#ifndef _GAMELAB_TEST_H
#define _GAMELAB_TEST_H

//
// test-suit:
//
//  TEST_CASE(testcase)
//  {
//  }
//  ...
//
//  RUN_TEST_SUIT();
//
// test-macro:
//
//  GL_TEST(expression)
//  GL_ERROR(message)
//  GL_TEST_EQ(expr1, expr2)
//
// errors:
//  int hh::report_errors()
//

#include <string>
#include <iostream>
#include <map>
#include <vector>

namespace gl
{
	typedef void (*test_case_t) ();

	class test_suit_t
	{
		public:
			typedef std::map<std::string, test_case_t> test_case_cont;

			void reg(const std::string& name, test_case_t test)
			{
				tests_[name] = test;
				test_names_.push_back(name);
			}

			void run(const std::string& name = "")
			{
				if (name.empty())
				{
					for (size_t i = 0; i < test_names_.size(); ++i)
					{
						test_case_cont::iterator it = tests_.find(test_names_[i]);
						if (it != tests_.end())
						{
							test_title(it->first);

							(it->second)();	
						}
						else
						{
							std::cerr << "can't find test-case:"  << name << std::endl;
						}
					}
				}
				else
				{
					test_case_cont::iterator it = tests_.find(name);
					if (it != tests_.end())
					{
						test_title(it->first);

						(it->second)();	
					}
					else
					{
						std::cerr << "can't find test-case:"  << name << std::endl;
					}
				}
			}

			void test_title(const std::string& name)
			{
				std::cout << "++++++++++++|" << name << "|+++++++++++" << std::endl;
			}

		private:
			test_case_cont tests_;

			std::vector<std::string> test_names_;
	};

	namespace detail
	{
		inline test_suit_t& test_suit()
		{
			static test_suit_t suit;

			return suit;
		}

		inline int & test_errors()
		{
			static int x = 0;
			return x;
		}

		inline void test_failed_impl(char const * expr, char const * file, int line, char const * function)
		{
			std::cerr << file << "(" << line << "): test '" << expr << "' failed in function '" << function << "'" << std::endl;
			++test_errors();
		}

		inline void error_impl(char const * msg, char const * file, int line, char const * function)
		{
			std::cerr << file << "(" << line << "): " << msg << " in function '" << function << "'" << std::endl;
			++test_errors();
		}

		template<class T, class U> 
			inline void test_eq_impl(char const * expr1, char const * expr2, char const * file, int line, char const * function, T const & t, U const & u )
			{
				if( t == u )
				{
				}
				else
				{
					std::cerr << file << "(" << line << "): test '" << expr1 << " == " << expr2;
					std::cerr << "' failed in function '" << function << "': " << "'" << t << "' != '" << u << "'" << std::endl;
					++test_errors();
				}
			}

	} // namespace detail

	struct test_case_register
	{
		test_case_register(const std::string& name, test_case_t test)
		{
			detail::test_suit().reg(name, test);
		}
	};

	inline int report_errors()
	{
		int errors = detail::test_errors();

		if( errors == 0 )
		{
			std::cerr << "No errors detected." << std::endl;
			return 0;
		}
		else
		{
			std::cerr << errors << " error" << (errors == 1? "": "s") << " detected." << std::endl;
			return 1;
		}
	}


} // namespace gl

#define GL_CURRENT_FUNCTION __PRETTY_FUNCTION__

#define TEST_CASE(testcase) void testcase(); gl::test_case_register testcase##_register(#testcase, testcase);void testcase()
#define RUN_TEST_CASE(name) gl::detail::test_suit().run(name)
#define RUN_TEST_CASE_BY_ARG(argc, argv) if(argc >=2 ) RUN_TEST_CASE(argv[1]); else RUN_TEST_CASE(""); gl::report_errors();
#define RUN_TEST_SUIT() int main(int argc, char** argv) { RUN_TEST_CASE_BY_ARG(argc, argv); std::cin.get(); }

#define GL_TEST(expr) ((expr)? (void)0: gl::detail::test_failed_impl(#expr, __FILE__, __LINE__, GL_CURRENT_FUNCTION))
#define GL_ERROR(msg) gl::detail::error_impl(msg, __FILE__, __LINE__, GL_CURRENT_FUNCTION)
#define GL_TEST_EQ(expr1,expr2) ( gl::detail::test_eq_impl(#expr1, #expr2, __FILE__, __LINE__, GL_CURRENT_FUNCTION, expr1, expr2) )

#endif // _GAMELAB_TEST_H
